package net.java.nioserver.sio;

import net.java.nioserver.IoLogUtil;
import net.java.nioserver.operations.IoEventListener;
import net.java.nioserver.operations.OpAccept;
import net.java.nioserver.utils.ChannelUtil;
import net.jcip.annotations.NotThreadSafe;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.nio.channels.AsynchronousCloseException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SocketChannel;

/**
 * @author Leonid Shlyapnikov
 */
@NotThreadSafe
class SioTask implements Runnable {

    private static final Logger log = LoggerFactory.getLogger(SioTask.class);

    private final IoEventListener listener;
    private SocketChannel channel;
    private final OpAccept opAccept;


    SioTask(OpAccept opAccept, IoEventListener listener, SocketChannel channel) {
        assert null != listener;
        assert null != channel;
        
        this.opAccept = opAccept;
        this.listener = listener;
        this.channel = channel;
    }

    public void run() {
        try {
            runOpAcceptIfConfigured();
            runMainLoop();
        } catch (InterruptedException e) {
            log.info("Interruption requested", e);
            ChannelUtil.close(channel);
            Thread.currentThread().interrupt();
        } catch (AsynchronousCloseException e) {
            IoLogUtil.logAsynchronousCloseException(e, channel, log);
        } catch (ClosedChannelException e) {
            IoLogUtil.logClosedChannelException(e, channel, log);
            ChannelUtil.close(channel);
        } catch (IOException e) {
            IoLogUtil.logIOException(e, listener, channel, log);
            ChannelUtil.close(channel);
        } catch (Exception e) {
            ChannelUtil.close(channel);
            throw new IllegalStateException("Unexpected exception dispatching event to: " + listener
                    + ", channel closed", e);
        }
    }

    private void runOpAcceptIfConfigured() throws IOException, InterruptedException {
        if (null != opAccept) {
            opAccept.handle(channel);
        }
    }

    private void runMainLoop() throws IOException, InterruptedException {
        while (true) {
            boolean runMore = listener.handle(channel);
            if (!runMore) {
                closeChannelBecauseFarEndDisconnected();
                return;
            }
        }
    }

    private void closeChannelBecauseFarEndDisconnected() {
        if (log.isInfoEnabled()) {
            log.info("Far end disconnected, closing channel: " + channel);
        }
        ChannelUtil.close(channel);
    }
}
